// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/output/ca_layer_overlay.h"

#include <algorithm>

#include "base/metrics/histogram_macros.h"
#include "cc/quads/render_pass_draw_quad.h"
#include "cc/quads/solid_color_draw_quad.h"
#include "cc/quads/stream_video_draw_quad.h"
#include "cc/quads/texture_draw_quad.h"
#include "cc/quads/tile_draw_quad.h"
#include "cc/resources/resource_provider.h"
#include "gpu/GLES2/gl2extchromium.h"

namespace cc {

namespace {

    // If there are too many RenderPassDrawQuads, we shouldn't use Core Animation to
    // present them as individual layers, since that potentially doubles the amount
    // of work needed to present them. cc has to blit them into an IOSurface, and
    // then Core Animation has to blit them to the final surface.
    // https://crbug.com/636884.
    const int kTooManyRenderPassDrawQuads = 30;

    // This enum is used for histogram states and should only have new values added
    // to the end before COUNT.
    enum CALayerResult {
        CA_LAYER_SUCCESS = 0,
        CA_LAYER_FAILED_UNKNOWN,
        CA_LAYER_FAILED_IO_SURFACE_NOT_CANDIDATE,
        CA_LAYER_FAILED_STREAM_VIDEO_NOT_CANDIDATE,
        CA_LAYER_FAILED_STREAM_VIDEO_TRANSFORM,
        CA_LAYER_FAILED_TEXTURE_NOT_CANDIDATE,
        CA_LAYER_FAILED_TEXTURE_Y_FLIPPED,
        CA_LAYER_FAILED_TILE_NOT_CANDIDATE,
        CA_LAYER_FAILED_QUAD_BLEND_MODE,
        CA_LAYER_FAILED_QUAD_TRANSFORM,
        CA_LAYER_FAILED_QUAD_CLIPPING,
        CA_LAYER_FAILED_DEBUG_BORDER,
        CA_LAYER_FAILED_PICTURE_CONTENT,
        CA_LAYER_FAILED_RENDER_PASS,
        CA_LAYER_FAILED_SURFACE_CONTENT,
        CA_LAYER_FAILED_YUV_VIDEO_CONTENT,
        CA_LAYER_FAILED_DIFFERENT_CLIP_SETTINGS,
        CA_LAYER_FAILED_DIFFERENT_VERTEX_OPACITIES,
        CA_LAYER_FAILED_RENDER_PASS_FILTER_SCALE,
        CA_LAYER_FAILED_RENDER_PASS_BACKGROUND_FILTERS,
        CA_LAYER_FAILED_RENDER_PASS_MASK,
        CA_LAYER_FAILED_RENDER_PASS_FILTER_OPERATION,
        CA_LAYER_FAILED_RENDER_PASS_SORTING_CONTEXT_ID,
        CA_LAYER_FAILED_TOO_MANY_RENDER_PASS_DRAW_QUADS,
        CA_LAYER_FAILED_COUNT,
    };

    bool FilterOperationSupported(const FilterOperation& operation)
    {
        switch (operation.type()) {
        case FilterOperation::GRAYSCALE:
        case FilterOperation::SEPIA:
        case FilterOperation::SATURATE:
        case FilterOperation::HUE_ROTATE:
        case FilterOperation::INVERT:
        case FilterOperation::BRIGHTNESS:
        case FilterOperation::CONTRAST:
        case FilterOperation::OPACITY:
        case FilterOperation::BLUR:
        case FilterOperation::DROP_SHADOW:
            return true;
        default:
            return false;
        }
    }

    static const FilterOperations* FiltersForPass(
        int render_pass_id,
        const RenderPassFilterList& filter_list)
    {
        auto it = std::lower_bound(
            filter_list.begin(), filter_list.end(),
            std::pair<int, FilterOperations*>(render_pass_id, nullptr));
        if (it != filter_list.end() && it->first == render_pass_id)
            return it->second;
        return nullptr;
    }

    CALayerResult FromRenderPassQuad(
        ResourceProvider* resource_provider,
        const RenderPassDrawQuad* quad,
        const RenderPassFilterList& render_pass_filters,
        const RenderPassFilterList& render_pass_background_filters,
        CALayerOverlay* ca_layer_overlay)
    {
        if (FiltersForPass(quad->render_pass_id, render_pass_background_filters)) {
            return CA_LAYER_FAILED_RENDER_PASS_BACKGROUND_FILTERS;
        }

        if (quad->shared_quad_state->sorting_context_id != 0)
            return CA_LAYER_FAILED_RENDER_PASS_SORTING_CONTEXT_ID;

        const FilterOperations* filters = FiltersForPass(quad->render_pass_id, render_pass_filters);
        if (filters) {
            for (const FilterOperation& operation : filters->operations()) {
                bool success = FilterOperationSupported(operation);
                if (!success)
                    return CA_LAYER_FAILED_RENDER_PASS_FILTER_OPERATION;
            }
        }

        ca_layer_overlay->rpdq = quad;
        ca_layer_overlay->contents_rect = gfx::RectF(0, 0, 1, 1);

        return CA_LAYER_SUCCESS;
    }

    CALayerResult FromStreamVideoQuad(ResourceProvider* resource_provider,
        const StreamVideoDrawQuad* quad,
        CALayerOverlay* ca_layer_overlay)
    {
        unsigned resource_id = quad->resource_id();
        if (!resource_provider->IsOverlayCandidate(resource_id))
            return CA_LAYER_FAILED_STREAM_VIDEO_NOT_CANDIDATE;
        ca_layer_overlay->contents_resource_id = resource_id;
        // TODO(ccameron): Support merging at least some basic transforms into the
        // layer transform.
        if (!quad->matrix.IsIdentity())
            return CA_LAYER_FAILED_STREAM_VIDEO_TRANSFORM;
        ca_layer_overlay->contents_rect = gfx::RectF(0, 0, 1, 1);
        return CA_LAYER_SUCCESS;
    }

    CALayerResult FromSolidColorDrawQuad(const SolidColorDrawQuad* quad,
        CALayerOverlay* ca_layer_overlay,
        bool* skip)
    {
        // Do not generate quads that are completely transparent.
        if (SkColorGetA(quad->color) == 0) {
            *skip = true;
            return CA_LAYER_SUCCESS;
        }
        ca_layer_overlay->background_color = quad->color;
        return CA_LAYER_SUCCESS;
    }

    CALayerResult FromTextureQuad(ResourceProvider* resource_provider,
        const TextureDrawQuad* quad,
        CALayerOverlay* ca_layer_overlay)
    {
        unsigned resource_id = quad->resource_id();
        if (!resource_provider->IsOverlayCandidate(resource_id))
            return CA_LAYER_FAILED_TEXTURE_NOT_CANDIDATE;
        if (quad->y_flipped) {
            // The anchor point is at the bottom-left corner of the CALayer. The
            // transformation that flips the contents of the layer without changing its
            // frame is the composition of a vertical flip about the anchor point, and a
            // translation by the height of the layer.
            ca_layer_overlay->shared_state->transform.preTranslate(
                0, ca_layer_overlay->bounds_rect.height(), 0);
            ca_layer_overlay->shared_state->transform.preScale(1, -1, 1);
        }
        ca_layer_overlay->contents_resource_id = resource_id;
        ca_layer_overlay->contents_rect = BoundingRect(quad->uv_top_left, quad->uv_bottom_right);
        ca_layer_overlay->background_color = quad->background_color;
        for (int i = 1; i < 4; ++i) {
            if (quad->vertex_opacity[i] != quad->vertex_opacity[0])
                return CA_LAYER_FAILED_DIFFERENT_VERTEX_OPACITIES;
        }
        ca_layer_overlay->shared_state->opacity *= quad->vertex_opacity[0];
        ca_layer_overlay->filter = quad->nearest_neighbor ? GL_NEAREST : GL_LINEAR;
        return CA_LAYER_SUCCESS;
    }

    CALayerResult FromTileQuad(ResourceProvider* resource_provider,
        const TileDrawQuad* quad,
        CALayerOverlay* ca_layer_overlay)
    {
        unsigned resource_id = quad->resource_id();
        if (!resource_provider->IsOverlayCandidate(resource_id))
            return CA_LAYER_FAILED_TILE_NOT_CANDIDATE;
        ca_layer_overlay->contents_resource_id = resource_id;
        ca_layer_overlay->contents_rect = quad->tex_coord_rect;
        ca_layer_overlay->contents_rect.Scale(1.f / quad->texture_size.width(),
            1.f / quad->texture_size.height());
        return CA_LAYER_SUCCESS;
    }

    class CALayerOverlayProcessor {
    public:
        CALayerResult FromDrawQuad(
            ResourceProvider* resource_provider,
            const gfx::RectF& display_rect,
            const DrawQuad* quad,
            const RenderPassFilterList& render_pass_filters,
            const RenderPassFilterList& render_pass_background_filters,
            CALayerOverlay* ca_layer_overlay,
            bool* skip,
            bool* render_pass_draw_quad)
        {
            if (quad->shared_quad_state->blend_mode != SkBlendMode::kSrcOver)
                return CA_LAYER_FAILED_QUAD_BLEND_MODE;

            // Early-out for invisible quads.
            if (quad->shared_quad_state->opacity == 0.f) {
                *skip = true;
                return CA_LAYER_SUCCESS;
            }

            // Enable edge anti-aliasing only on layer boundaries.
            //     ca_layer_overlay->edge_aa_mask = 0;
            //     if (quad->IsLeftEdge())
            //       ca_layer_overlay->edge_aa_mask |= GL_CA_LAYER_EDGE_LEFT_CHROMIUM;
            //     if (quad->IsRightEdge())
            //       ca_layer_overlay->edge_aa_mask |= GL_CA_LAYER_EDGE_RIGHT_CHROMIUM;
            //     if (quad->IsBottomEdge())
            //       ca_layer_overlay->edge_aa_mask |= GL_CA_LAYER_EDGE_BOTTOM_CHROMIUM;
            //     if (quad->IsTopEdge())
            //       ca_layer_overlay->edge_aa_mask |= GL_CA_LAYER_EDGE_TOP_CHROMIUM;
            DebugBreak();

            if (most_recent_shared_quad_state_ != quad->shared_quad_state) {
                most_recent_shared_quad_state_ = quad->shared_quad_state;
                most_recent_overlay_shared_state_ = new CALayerOverlaySharedState;
                // Set rect clipping and sorting context ID.
                most_recent_overlay_shared_state_->sorting_context_id = quad->shared_quad_state->sorting_context_id;
                most_recent_overlay_shared_state_->is_clipped = quad->shared_quad_state->is_clipped;
                most_recent_overlay_shared_state_->clip_rect = gfx::RectF(quad->shared_quad_state->clip_rect);

                most_recent_overlay_shared_state_->opacity = quad->shared_quad_state->opacity;
                most_recent_overlay_shared_state_->transform = quad->shared_quad_state->quad_to_target_transform.matrix();
            }
            ca_layer_overlay->shared_state = most_recent_overlay_shared_state_;

            ca_layer_overlay->bounds_rect = gfx::RectF(quad->rect);

            *render_pass_draw_quad = quad->material == DrawQuad::RENDER_PASS;
            switch (quad->material) {
            case DrawQuad::TEXTURE_CONTENT:
                return FromTextureQuad(resource_provider,
                    TextureDrawQuad::MaterialCast(quad),
                    ca_layer_overlay);
            case DrawQuad::TILED_CONTENT:
                return FromTileQuad(resource_provider, TileDrawQuad::MaterialCast(quad),
                    ca_layer_overlay);
            case DrawQuad::SOLID_COLOR:
                return FromSolidColorDrawQuad(SolidColorDrawQuad::MaterialCast(quad),
                    ca_layer_overlay, skip);
            case DrawQuad::STREAM_VIDEO_CONTENT:
                return FromStreamVideoQuad(resource_provider,
                    StreamVideoDrawQuad::MaterialCast(quad),
                    ca_layer_overlay);
            case DrawQuad::DEBUG_BORDER:
                return CA_LAYER_FAILED_DEBUG_BORDER;
            case DrawQuad::PICTURE_CONTENT:
                return CA_LAYER_FAILED_PICTURE_CONTENT;
            case DrawQuad::RENDER_PASS:
                return FromRenderPassQuad(
                    resource_provider, RenderPassDrawQuad::MaterialCast(quad),
                    render_pass_filters, render_pass_background_filters,
                    ca_layer_overlay);
            case DrawQuad::SURFACE_CONTENT:
                return CA_LAYER_FAILED_SURFACE_CONTENT;
            case DrawQuad::YUV_VIDEO_CONTENT:
                return CA_LAYER_FAILED_YUV_VIDEO_CONTENT;
            default:
                break;
            }

            return CA_LAYER_FAILED_UNKNOWN;
        }

    private:
        const SharedQuadState* most_recent_shared_quad_state_ = nullptr;
        scoped_refptr<CALayerOverlaySharedState> most_recent_overlay_shared_state_;
    };

} // namespace

CALayerOverlay::CALayerOverlay()
    : filter(GL_LINEAR)
{
}

CALayerOverlay::CALayerOverlay(const CALayerOverlay& other) = default;

CALayerOverlay::~CALayerOverlay() { }

bool ProcessForCALayerOverlays(
    ResourceProvider* resource_provider,
    const gfx::RectF& display_rect,
    const QuadList& quad_list,
    const RenderPassFilterList& render_pass_filters,
    const RenderPassFilterList& render_pass_background_filters,
    CALayerOverlayList* ca_layer_overlays)
{
    CALayerResult result = CA_LAYER_SUCCESS;
    ca_layer_overlays->reserve(quad_list.size());

    int render_pass_draw_quad_count = 0;
    CALayerOverlayProcessor processor;
    for (auto it = quad_list.BackToFrontBegin(); it != quad_list.BackToFrontEnd();
         ++it) {
        const DrawQuad* quad = *it;
        CALayerOverlay ca_layer;
        bool skip = false;
        bool render_pass_draw_quad = false;
        result = processor.FromDrawQuad(resource_provider, display_rect, quad,
            render_pass_filters,
            render_pass_background_filters, &ca_layer,
            &skip, &render_pass_draw_quad);
        if (result != CA_LAYER_SUCCESS)
            break;

        if (render_pass_draw_quad) {
            ++render_pass_draw_quad_count;
            if (render_pass_draw_quad_count > kTooManyRenderPassDrawQuads) {
                result = CA_LAYER_FAILED_TOO_MANY_RENDER_PASS_DRAW_QUADS;
                break;
            }
        }

        if (skip)
            continue;

        // It is not possible to correctly represent two different clipping settings
        // within one sorting context.
        if (!ca_layer_overlays->empty()) {
            const CALayerOverlay& previous_ca_layer = ca_layer_overlays->back();
            if (ca_layer.shared_state->sorting_context_id && previous_ca_layer.shared_state->sorting_context_id == ca_layer.shared_state->sorting_context_id) {
                if (previous_ca_layer.shared_state->is_clipped != ca_layer.shared_state->is_clipped || previous_ca_layer.shared_state->clip_rect != ca_layer.shared_state->clip_rect) {
                    result = CA_LAYER_FAILED_DIFFERENT_CLIP_SETTINGS;
                    break;
                }
            }
        }

        ca_layer_overlays->push_back(ca_layer);
    }

    UMA_HISTOGRAM_ENUMERATION("Compositing.Renderer.CALayerResult", result,
        CA_LAYER_FAILED_COUNT);

    if (result != CA_LAYER_SUCCESS) {
        ca_layer_overlays->clear();
        return false;
    }
    return true;
}

} // namespace cc
